package com.hmdp.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.lang.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.common.Constants;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.GeoUtils;
import com.hmdp.utils.RedisCache;
import com.hmdp.utils.SystemConstants;
import lombok.RequiredArgsConstructor;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@RequiredArgsConstructor
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    private final RedisCache redisCache;

    private final GeoUtils geoUtils;

    @Override
    public Result queryById(Long id) {
        // 判断Redis中是否有店铺信息
        String key = Constants.CACHE_SHOP_KEY + id;
        Shop data = redisCache.safeGet(key, Shop.class, id, this::getById, Constants.CACHE_DATA_TTL, TimeUnit.MINUTES);
        return Result.ok(data);
    }

    @Override
    public Result queryByTypeId(Integer typeId, Integer current, Double x, Double y) {
        // 因为用户的位置会不断变动，导致查询结果的顺序会不断变化，所以这类查询不适合缓存。
        // 查看缓存中是否有按照距离排好序的商铺信息
        String key = Constants.RADIUS_SHOP_KEY + typeId;
        Tuple tuple = redisCache.getTuple(key,
                new TypeReference<List<Long>>() {},
                new TypeReference<List<Double>>() {});
        if (tuple == null) {
            LambdaQueryWrapper<Shop> shopLambdaQueryWrapper = Wrappers.lambdaQuery(Shop.class);
            shopLambdaQueryWrapper.eq(Shop::getTypeId, typeId);
            List<Shop> shops = this.list(shopLambdaQueryWrapper);
            String shop_geo_key = Constants.SHOP_GEO_KEY + typeId;
            // 将店铺全部加到GEO数据结构中
            shops.forEach(shop -> {
                geoUtils.addLocation(shop_geo_key, shop.getX(), shop.getY(), shop.getId().toString(),
                        Constants.SHOP_GEO_TTL, TimeUnit.DAYS);
            });
            // 获取附近店铺（按距离排好序的）
            tuple = geoUtils.getNearLocation(shop_geo_key, x, y);
            // 缓存到redis
            redisCache.set(key, tuple, Constants.USER_GEO_TTL, TimeUnit.SECONDS);
        }

        ArrayList<Long> shopIdList = tuple.get(0);
        List<Double> disValues = tuple.get(1);

        int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
        int end = from + SystemConstants.DEFAULT_PAGE_SIZE;

        if (shopIdList.size() <= from) {
            return Result.ok(Collections.emptyList());
        }
        ArrayList<Shop> shopArrayList = new ArrayList<>();
        for (int i = from; i < Math.min(end, shopIdList.size()); i++) {
            Long shopId = shopIdList.get(i);
            Shop shop = redisCache.safeGet(Constants.CACHE_SHOP_KEY + shopId, Shop.class, shopId, this::getById,
                    Constants.CACHE_DATA_TTL, TimeUnit.MINUTES);
            Double dis = disValues.get(i);
            shop.setDistance(dis);
            shopArrayList.add(shop);
        }

        return Result.ok(shopArrayList);
    }

    @Override
    public Result queryByName(String name, Integer current) {
        // 连锁店同名且有很多家，因此也需要分页
        String key = Constants.CACHE_SHOP_KEY + "name:" + name + ":page:" + current;
        Page shopList = redisCache.safeGet(key, Page.class, name, (queryName) -> this.query()
                .eq("name", name)
                .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE)), Constants.CACHE_DATA_TTL, TimeUnit.MINUTES);

        if (shopList != null && shopList.getTotal() > 0) {
            return Result.ok(shopList.getRecords());
        }
        return Result.ok(new Page<>().getRecords());
    }
}
